using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityModel;
using IdentityServer4;
using IdentityServer4.Events;
using IdentityServer4.Extensions;
using IdentityServer4.Services;
using IdentityServer4.Stores;
using IdentityServer4.Test;
using Idp_Login.DbModel;
using Idp_Login.Models;
using Idp_Login.Service;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace Idp_Login.Controllers
{
    public class AccountController : Controller
    {
        private IAdminService _adminService;//自己写的操作数据库Admin表的service
        private readonly IIdentityServerInteractionService _interaction;
        private readonly IClientStore _clientStore;
        private readonly IAuthenticationSchemeProvider _schemeProvider;
        private readonly IEventService _events;
        public AccountController(IIdentityServerInteractionService interaction,
            IClientStore clientStore,
            IAuthenticationSchemeProvider schemeProvider,
            IEventService events,
            IAdminService adminService)
        {
            _interaction = interaction;
            _clientStore = clientStore;
            _schemeProvider = schemeProvider;
            _events = events;
            _adminService = adminService;
        }

        /// <summary>
        /// 登录页面
        /// </summary>
        [HttpGet]
        public IActionResult Login(string returnUrl = null)
        {
            ViewData["returnUrl"] = returnUrl;
            return View();
        }

        /// <summary>
        /// 登录post回发处理
        /// </summary>
        [HttpPost]
        public async Task<IActionResult> Login(string userName, string password, string tenantCode, string returnUrl = null)
        {
            Console.WriteLine($"{nameof(AccountController)} -  {nameof(Login)} 端口号:" + HttpContext.Request.Host.Port);
            ViewData["returnUrl"] = returnUrl;
            Admin user = await _adminService.GetByUserNameAndPwd(userName, password);
            if (user == null)
            {
                return View();
            }

            //ClaimsIdentity ci = new ClaimsIdentity(DefaultAuthenticationTypes.ApplicationCookie);
            //ci.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, model.UserName));
            //ci.AddClaim(new Claim(ClaimTypes.NameIdentifier, model.UID));
            //ci.AddClaim(new Claim("HspUID", model.HspUID));
            //AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = IsRemeber }, ci);


            //如果用户选择“记住我”，则仅在此处设置显式到期。 // if (AccountOptions.AllowRememberLogin && model.RememberLogin)
            //否则，我们将依赖Cookie中间件中配置的到期时间(这个 cookie 只是当前回话有效。)。
            AuthenticationProperties props = new AuthenticationProperties
            {
                IsPersistent = true,//表示 cookie 是否是持久化的以便它在不同的 request 之间传送。设置了ExpireTimeSpan或ExpiresUtc是必须的。
                                    //ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(30))//cookie 的绝对过期时间，会覆盖ExpireTimeSpan的设置。
                                    // ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) //这个是30天
                                    //AllowRefresh = <bool>,
                                    // Refreshing the authentication session should be allowed.

                //IssuedUtc = <DateTimeOffset>,
                //  凭证认证的时间。

                //RedirectUri = <string>
                //http 跳转的时候的路径。
            };


            // issue authentication cookie with subject ID and username
            //创建一个加密过的 cookie 然后把他添加到当前的 response 中。 
            //用于authentication(认证)
            //没有设置AuthenticationScheme的话会使用默认的 scheme。

            //var claims = new List<Claim>
            var additionalLocalClaims = new List<Claim>
            {
                //这里写的Claim 在 CustomProfileService 的 context.Subject.Claims 中会出现
                //如果 如果要做到特定的 access_token / id_token 中出现
                //实现方法: 1. 写入redis, 2. 判断 caller, 然后写入

                //new Claim("tenantCode", tenantCode??""),
                //new Claim("UserId", (string) user.Id.ToString() ?? ""),
                //new Claim("UserName", (string) user.UserName?? ""),
                ////new Claim("UserNickName", (string) output.UserNickName ?? ""),
                //new Claim("TenantId",  "19"),
                //new Claim("TenantCode", "1099")

                //new Claim("Balance", "1.23"),
            };

            //await HttpContext.SignInAsync(user.Id.ToString(), user.UserName, props, claims);   //id4 v3


            // issue authentication cookie with subject ID and username
            var isuser = new IdentityServerUser(user.Id.ToString())
            {
                DisplayName = user.UserName,
                AdditionalClaims = additionalLocalClaims, //claims
            };


            await HttpContext.SignInAsync(isuser, props);

            // id4 v4 issue authentication cookie with subject ID and username
            //var isuser = new IdentityServerUser(user.Id.ToString())
            //{
            //    DisplayName = user.UserName,
            //    AdditionalClaims = claims,
            //};
            //await HttpContext.SignInAsync(isuser, props);


            if (returnUrl == null)
            {
                return Content("登陆结束, 由于没有跳转页,所以无法304,可以手动打开要访问的页面");
            }

            // request for a local page
            if (Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
            return Redirect(returnUrl);

        }


        /// <summary>
        /// Show logout page
        /// </summary>
        [HttpGet]
        public async Task<IActionResult> Logout(string logoutId)
        {

            // 如果注销请求已通过IdentityServer正确验证，则我们无需显示提示，而可以直接将用户注销。
            if (User?.Identity.IsAuthenticated != true)
            {
                // 如果用户未通过身份验证，则仅显示注销页面
                return new ContentResult() { Content = "如果用户未通过身份验证，则仅显示注销页面" };
            }

            var context = await _interaction.GetLogoutContextAsync(logoutId);
            if (context?.ShowSignoutPrompt == false)
            {
                var logout = Logout(new LogoutInputModel() { LogoutId = logoutId }).Result;

                if (logout is JsonResult _logout)
                {
                    if (((dynamic)_logout.Value).Result is LoggedOutViewModel _vm)
                    {
                        if (!string.IsNullOrEmpty(_vm.PostLogoutRedirectUri))
                        {
                            return Redirect(_vm.PostLogoutRedirectUri);
                        }

                        var refererUrl = Request.Headers["Referer"].ToString();
                        UrlOper.ParseUrl(refererUrl, out var baseUrl, out var nvc);

                        if (nvc.HasKeys() && nvc.AllKeys.Contains("post_logout_redirect_uri"))
                        {
                            var uri = nvc.GetValues("post_logout_redirect_uri")?.First();
                            _vm.PostLogoutRedirectUri = UrlOper.UrlDecrypt(uri);
                        }

                        if (!string.IsNullOrEmpty(_vm.PostLogoutRedirectUri))
                        {
                            return Redirect(_vm.PostLogoutRedirectUri);
                        }
                    }
                }

                return logout;// 很安全的 自动注销
            }

            //一个咨询用户的操作
            return new JsonResult(new { Message = "真的要注销么???", Result = new { logoutId } });
        }

        /// <summary>
        /// Handle logout page postback
        /// </summary>
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Logout(LogoutInputModel model)
        {
            // build a model so the logged out page knows what to display
            var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);

            if (User?.Identity.IsAuthenticated == true)
            {
                // delete local authentication cookie
                await HttpContext.SignOutAsync();

                // raise the logout event
                await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
            }

            // check if we need to trigger sign-out at an upstream identity provider
            if (vm.TriggerExternalSignout)
            {
                // build a return URL so the upstream provider will redirect back
                // to us after the user has logged out. this allows us to then
                // complete our single sign-out processing.
                string url = Url.Action("Logout", new { logoutId = vm.LogoutId });

                // this triggers a redirect to the external provider for sign-out
                return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
            }

            //return View("LoggedOut", vm);
            //return new ContentResult() { Content = "很安全的 自动注销" };
            return new JsonResult(new { Message = "很安全的 自动注销", Result = vm });
        }

        private async Task<LoggedOutViewModel> BuildLoggedOutViewModelAsync(string logoutId)
        {
            // get context information (client name, post logout redirect URI and iframe for federated signout)
            var logout = await _interaction.GetLogoutContextAsync(logoutId);

            var vm = new LoggedOutViewModel
            {
                AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
                PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
                ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
                SignOutIframeUrl = logout?.SignOutIFrameUrl,
                LogoutId = logoutId
            };

            if (User?.Identity.IsAuthenticated == true)
            {
                var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
                if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider)
                {
                    var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
                    if (providerSupportsSignout)
                    {
                        if (vm.LogoutId == null)
                        {
                            // if there's no current logout context, we need to create one
                            // this captures necessary info from the current logged in user
                            // before we signout and redirect away to the external IdP for signout
                            vm.LogoutId = await _interaction.CreateLogoutContextAsync();
                        }

                        vm.ExternalAuthenticationScheme = idp;
                    }
                }
            }

            return vm;
        }
    }
}
